package org.vaadin.elements.impl; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import net.sf.cglib.proxy.CallbackHelper; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import net.sf.cglib.proxy.NoOp; import org.vaadin.elements.Element; import org.vaadin.elements.Elements; import org.vaadin.elements.UpdatedBy; public class ElementReflectHelper { public static NodeImpl wrap(org.jsoup.nodes.Node soupNode) { return wrap(soupNode, null); } public static NodeImpl wrap(org.jsoup.nodes.Node soupNode, Class<? extends Element> elementType) { if (soupNode instanceof org.jsoup.nodes.TextNode) { return new TextNodeImpl((org.jsoup.nodes.TextNode) soupNode); // } else if (soupNode instanceof org.jsoup.nodes.DataNode) { // return Elements.createText(((org.jsoup.nodes.DataNode) soupNode) // .getWholeData()); } else if (soupNode instanceof org.jsoup.nodes.Element) { org.jsoup.nodes.Element soupElement = (org.jsoup.nodes.Element) soupNode; String tag = soupElement.tagName(); if (elementType == null) { elementType = Elements.getRegisteredClass(tag); } if (elementType == null) { return new ElementImpl(soupElement); } else { return (NodeImpl) wrapElement(elementType, soupElement); } } else { throw new RuntimeException(soupNode.getClass().getName()); } } private static <T extends Element> T wrapElement(Class<T> type, org.jsoup.nodes.Element soupElement) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(ElementImpl.class); enhancer.setInterfaces(new Class[] { type }); enhancer.setClassLoader(Elements.class.getClassLoader()); MethodInterceptor methodInterceptor = new MethodInterceptor() { @Override public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable { ElementImpl element = (ElementImpl) obj; String name = method.getName(); if (isGetter(method)) { name = ElementReflectHelper.getAttributeName(name); Class<?> type = method.getReturnType(); if (type == String.class) { return element.getAttribute(name); } else if (type == double.class) { String value = element.getAttribute(name); if (value == null || value.isEmpty()) { return Double.valueOf(0); } else { return Double.valueOf(value); } } else if (type == boolean.class) { return element.hasAttribute(name); } } else if (name.startsWith("set") && method.getParameterCount() == 1) { name = ElementReflectHelper.getAttributeName(name); Class<?> type = method.getParameterTypes()[0]; if (type == String.class) { element.setAttribute(name, (String) args[0]); } else if (type == double.class) { element.setAttribute(name, args[0].toString()); } else if (type == boolean.class) { element.setAttribute(name, ((Boolean) args[0]).booleanValue()); } else { throw new RuntimeException( "Property type not supported for " + method); } return null; } throw new RuntimeException("Unsupported method: " + method); } }; CallbackHelper callbackHelper = new CallbackHelper(ElementImpl.class, new Class[] { type }) { @Override protected Object getCallback(Method method) { if (method.isDefault() || !Modifier.isAbstract(method.getModifiers())) { return NoOp.INSTANCE; } else { return methodInterceptor; } } }; enhancer.setCallbackFilter(callbackHelper); enhancer.setCallbacks(callbackHelper.getCallbacks()); T instance = type.cast(enhancer.create( new Class[] { org.jsoup.nodes.Element.class }, new Object[] { soupElement })); for (Method method : type.getMethods()) { if (isGetter(method)) { for (UpdatedBy updatedBy : method .getAnnotationsByType(UpdatedBy.class)) { // Binding actually happens on the property level in the DOM instance.bindAttribute(getPropertyName(method.getName()), updatedBy.value()); } } } return instance; } private static boolean isGetter(Method method) { String name = method.getName(); return (name.startsWith("get") || name.startsWith("is")) && method.getParameterCount() == 0; } public static String getPropertyName(String name) { name = name.replaceAll("^(get|set|is)", ""); // TODO locale for lower case name = Character.toLowerCase(name.charAt(0)) + name.substring(1); return name; } private static String getAttributeName(String methodName) { String propertyName = getPropertyName(methodName); // Convert camelCase to dash-case return propertyName.replaceAll("([A-Z])", "-$1").toLowerCase(); } }